Laboratory 3 Part 2: Linear Systems Theory Applied to Optical Imaging Systems
Measure the Point Spread Function of a Microscope

import os
import sys
sys.path.insert(0,r'BaITools')
sys.path.insert(0,r'dlls')
import numpy as np
from time import sleep
import matplotlib.pyplot as plt
from IPython.display import display, clear_output
import cv2
plt.rcParams['font.size'] = 16
from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable
from zelux import ZeluxCamera as Camera
--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) Cell In[13], line 13 11 plt.rcParams['font.size'] = 16 12 from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable ---> 13 from zelux import ZeluxCamera as Camera ModuleNotFoundError: No module named 'zelux'
print('Camera initialized as c')
c = Camera()
c.open()
c.get_properties()
c.set_exposure(10)
c.get_exposure()
c.set_framerate(2)
c.get_framerate()
c.set_exposure(30)
Camera initialized as c Camera Model Thorlabs CS165MU Pixel Size 3.45 microns Image size [1440, 1080] Exposure 30.31 ms Exposure Range [0.06, 26843.42] ms Framerate 18.0 Frame Range [0.9, 18.0]
c.close()
def average_images(num_images):
image_10bit_stack = []
for i in range(num_images):
image_10bit_stack.append(c.capture().copy())
if (i+1) % 10 == 0:
print("image number taken:", i+1)
average_image_10bit = np.mean(image_10bit_stack, axis=0)
return average_image_10bit
def capture_image(save_name="False"):
image_10bit = c.capture().copy()
x1,y1,x2,y2 = 650, 300, 850, 500
cropped_image_10bit = image_10bit[y1:y2, x1:x2]
fig, axes = plt.subplots(1, 2, figsize=(18, 5))
im0 = axes[0].imshow(image_10bit, origin='lower', cmap='jet')
axes[0].set_title(f"Original Image")
axes[0].set_xlabel("X")
axes[0].set_ylabel("Y")
fig.colorbar(im0, ax=axes[0], label='Magnitude')
im1 = axes[1].imshow(cropped_image_10bit, origin='lower', cmap='jet')
axes[1].set_title(f"Cropped Image")
axes[1].set_xlabel("X")
axes[1].set_ylabel("Y")
fig.colorbar(im1, ax=axes[1], label='Magnitude')
plt.show()
return image_10bit, cropped_image_10bit
def continuous_capture_jet(save_name="saved_image.png"):
c.set_exposure(1000)
while True:
cv2.imshow('Camera', cv2.applyColorMap(cv2.normalize(c.capture().copy(), None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U), cv2.COLORMAP_JET)) #show 8 bit image
if cv2.waitKey(30) & 0xFF == ord('s'):
image = c.capture().copy()
fig, ax = plt.subplots(1, figsize=(12, 5))
x = ax.imshow(image, cmap='jet')
fig.colorbar(x, ax=ax)
ax.set_xlabel('X Axis pixels')
ax.set_ylabel('Y Axis pixels')
plt.savefig(save_name)
if cv2.waitKey(30) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
return image
def continuous_capture(save_name="saved_image.png"):
while True:
cv2.imshow('Camera', cv2.normalize(c.capture().copy(), None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)) #show 8 bit image
if cv2.waitKey(30) & 0xFF == ord('s'):
image = c.capture().copy()
fig, ax = plt.subplots(1, figsize=(12, 5))
x = ax.imshow(image, cmap='jet')
fig.colorbar(x, ax=ax)
ax.set_xlabel('X Axis pixels')
ax.set_ylabel('Y Axis pixels')
plt.savefig(save_name)
if cv2.waitKey(30) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
return image
def get_magnification():
image_10bit = continuous_capture()
%matplotlib widget
fig = plt.figure(figsize=(10, 10))
ax = fig.add_axes([0.1, 0.3, 0.6, 0.6])
ax_divider = make_axes_locatable(ax)
cax = ax_divider.append_axes("right", size="7%", pad="2%")
im = ax.imshow(image_10bit, cmap='gray')
cb = fig.colorbar(im, cax=cax)
hfig = display(fig, display_id=True)
plt.show()
def calculate_magnification(x1, y1, x2, y2, grid_size=100e-6, camera_size = 3.45e-6):
d_squared = (x1-x2)**2 + (y1-y2)**2
d = np.sqrt(d_squared) #number of pixels between grid_size um
pixel_size_object = grid_size / d
print('Pixel size of camera:', camera_size)
print('Pixel size in object plane:', pixel_size_object)
magnification = camera_size / pixel_size_object
print('Magnification:', magnification)
return magnification, pixel_size_object
def calculate_objectsamplingfrequency(pixel_size_object):
object_sampling_frequency = 1 / pixel_size_object # in 1 / m
object_sampling_frequency_mm = object_sampling_frequency / 1000
print('Object sampling frequency in mm:', object_sampling_frequency_mm)
return object_sampling_frequency
def show_image_fourier(image_10bit, name=""):
D_shifted = ifftshift(image_10bit)
D_fourier = fft2(D_shifted)
D_fourier_shifted = fftshift(D_fourier)
D_fourier_magnitude = np.abs(D_fourier_shifted)
D_fourier_magnitude_log = np.log(D_fourier_magnitude)
fig, axes = plt.subplots(1, 2, figsize=(18, 5))
#image
im0 = axes[0].imshow(image_10bit, origin='lower', cmap='gray')
axes[0].set_title(f"Original Image - {name}")
axes[0].set_xlabel("Frequency X (1/mm)")
axes[0].set_ylabel("Frequency Y (1/mm)")
fig.colorbar(im0, ax=axes[0], label='Magnitude')
#fourier
im1 = axes[1].imshow(D_fourier_magnitude_log, origin='lower', cmap='gray')
axes[1].set_title(f"Fourier Transform Magnitude Log Scale - {name}")
axes[1].set_xlabel("Frequency X (1/mm)")
axes[1].set_ylabel("Frequency Y (1/mm)")
fig.colorbar(im1, ax=axes[1], label='Magnitude (log scale)')
plt.show()
return D_fourier_magnitude_log
def zoom_image(image, x1, y1, x2, y2):
zoomed = image[y1:y2, x1:x2]
plt.figure(figsize=(7,7))
plt.imshow(zoomed, cmap='jet')
plt.title('Zoomed-In Image')
plt.xlabel('X Pixels')
plt.ylabel('Y Pixels')
plt.show()
plt.savefig('zoomed_image.png')
return zoomed
def show_zoomed(image, darkfield, x1, y1, x2, y2,
image_name="Original Image",
cropped_image_name="Cropped Image",
mm_per_pixel=(3.45e-6*1000), vmin=0, vmax=200):
# Cropping and subtracting darkfield
cropped_image = image[y1:y2, x1:x2]
image_absolute = image - darkfield
cropped_image_absolute = cropped_image - darkfield[y1:y2, x1:x2]
# Extents in mm
full_height, full_width = image.shape
cropped_height, cropped_width = cropped_image.shape
extent = [0, full_width * mm_per_pixel, 0, full_height * mm_per_pixel]
cropped_extent = [0, cropped_width * mm_per_pixel, 0, cropped_height * mm_per_pixel]
# Set up plot
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# Helper to format axes
def format_axis(ax):
ax.set_xlabel("X (mm)", fontsize=12)
ax.set_ylabel("Y (mm)", fontsize=12)
ax.ticklabel_format(style='plain', axis='x')
ax.ticklabel_format(style='plain', axis='y')
ax.tick_params(labelsize=10)
# Plot 1 - Full Image
im0 = axes[0][0].imshow(image, origin='lower', cmap='jet', extent=extent, vmin=vmin, vmax=vmax)
axes[0][0].set_title(image_name, fontsize=14)
format_axis(axes[0][0])
fig.colorbar(im0, ax=axes[0][0], label='Magnitude')
# Plot 2 - Cropped Image
im1 = axes[1][0].imshow(cropped_image, origin='lower', cmap='jet', extent=cropped_extent, vmin=vmin, vmax=vmax)
axes[1][0].set_title(cropped_image_name, fontsize=14)
format_axis(axes[1][0])
fig.colorbar(im1, ax=axes[1][0], label='Magnitude')
# Plot 3 - Full Image - Darkfield Subtracted
im2 = axes[0][1].imshow(image_absolute, origin='lower', cmap='jet', extent=extent, vmin=vmin, vmax=vmax)
axes[0][1].set_title(f"{image_name} - Darkfield", fontsize=14)
format_axis(axes[0][1])
fig.colorbar(im2, ax=axes[0][1], label='Magnitude')
# Plot 4 - Cropped Image - Darkfield Subtracted
im3 = axes[1][1].imshow(cropped_image_absolute, origin='lower', cmap='jet', extent=cropped_extent, vmin=vmin, vmax=vmax)
axes[1][1].set_title(f"{cropped_image_name} - Darkfield", fontsize=14)
format_axis(axes[1][1])
fig.colorbar(im3, ax=axes[1][1], label='Magnitude')
plt.tight_layout()
plt.show()
return image, image_absolute, cropped_image, cropped_image_absolute
get_magnification()
x1, y1, x2, y2 = 20, 777, 1302, 748
magnification, pixel_size_object = calculate_magnification(x1, y1, x2, y2, grid_size=800e-6, camera_size=3.45e-6)
calculate_objectsamplingfrequency(pixel_size_object)
Pixel size of camera: 3.45e-06 Pixel size in object plane: 6.238653637948366e-07 Magnification: 5.5300393325482995 Object sampling frequency in mm: 1602.909951463275
1602909.9514632751
%matplotlib inline
focus_image = continuous_capture()
--------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) C:\Users\BUILD_~1\AppData\Local\Temp/ipykernel_37852/2304232838.py in <module> ----> 1 focus_image = continuous_capture() C:\Users\BUILD_~1\AppData\Local\Temp/ipykernel_37852/185871749.py in continuous_capture(save_name) 64 break 65 cv2.destroyAllWindows() ---> 66 return image 67 68 def get_magnification(): UnboundLocalError: local variable 'image' referenced before assignment
c.set_exposure(1000)
c.set_framerate(1)
pinhole = continuous_capture_jet()
--------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) C:\Users\BUILD_~1\AppData\Local\Temp/ipykernel_37852/4271621151.py in <module> 3 4 ----> 5 pinhole = continuous_capture_jet() C:\Users\BUILD_~1\AppData\Local\Temp/ipykernel_37852/1197984356.py in continuous_capture_jet(save_name) 47 break 48 cv2.destroyAllWindows() ---> 49 return image 50 51 def continuous_capture(save_name="saved_image.png"): UnboundLocalError: local variable 'image' referenced before assignment
capture_image("pinhole_nofilter")
(array([[2, 2, 2, ..., 2, 2, 2],
[3, 1, 2, ..., 2, 1, 2],
[2, 3, 2, ..., 2, 1, 2],
...,
[3, 3, 2, ..., 2, 3, 3],
[2, 1, 2, ..., 2, 2, 2],
[2, 1, 2, ..., 3, 3, 2]], dtype=uint16),
array([[2, 2, 2, ..., 2, 2, 2],
[1, 2, 3, ..., 1, 2, 2],
[5, 2, 2, ..., 2, 2, 1],
...,
[3, 3, 2, ..., 3, 2, 2],
[2, 3, 3, ..., 3, 3, 3],
[3, 3, 3, ..., 3, 2, 3]], dtype=uint16))
green_darkfield = average_images(50)
np.save('green_darkfield', green_darkfield)
image number taken: 10 image number taken: 20 image number taken: 30 image number taken: 40 image number taken: 50
print(np.shape(green_darkfield))
(1080, 1440)
x1,y1,x2,y2 = 650, 300, 850, 500
vmin, vmax = 0, 10
show_zoomed(green_darkfield, green_darkfield, x1, y1, x2, y2, image_name='Green Darkfield', cropped_image_name='Cropped Green Darkfield', mm_per_pixel = (6.230210280811735e-07*1000), vmin=vmin, vmax=vmax)
(array([[1.96, 2.1 , 1.98, ..., 2.52, 2.42, 2.26],
[2.2 , 2.04, 1.9 , ..., 2.44, 2.06, 2.2 ],
[1.94, 2.22, 1.92, ..., 2.36, 2.68, 2.52],
...,
[1.96, 2.48, 1.94, ..., 2.34, 2.8 , 2.36],
[2.22, 2.34, 2.12, ..., 2.56, 2.44, 2.38],
[2.36, 2.12, 2.26, ..., 2.32, 2.58, 2.5 ]]),
array([[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.]]),
array([[2.46, 2.68, 2.52, ..., 2.4 , 2.72, 2.66],
[2.66, 2.68, 2.5 , ..., 2.14, 2.34, 2.44],
[4.52, 2.54, 2.56, ..., 2.58, 2.74, 2.68],
...,
[2.6 , 2.56, 2.34, ..., 2.52, 2.68, 2.66],
[2.36, 2.6 , 2.48, ..., 2.5 , 2.6 , 2.54],
[2.64, 2.78, 2.38, ..., 2.44, 2.46, 2.6 ]]),
array([[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.]]))
wide_green = average_images(100)
np.save('wide_green', wide_green)
image number taken: 10 image number taken: 20 image number taken: 30 image number taken: 40 image number taken: 50 image number taken: 60 image number taken: 70 image number taken: 80 image number taken: 90 image number taken: 100
capture_image("pinhole_greenfilter")
(array([[2, 3, 1, ..., 2, 2, 3],
[2, 2, 2, ..., 3, 2, 2],
[3, 3, 2, ..., 3, 1, 3],
...,
[2, 2, 2, ..., 2, 3, 2],
[3, 2, 2, ..., 2, 2, 3],
[2, 1, 2, ..., 2, 3, 2]], dtype=uint16),
array([[2, 2, 3, ..., 2, 3, 2],
[3, 2, 2, ..., 1, 3, 2],
[4, 2, 3, ..., 2, 1, 3],
...,
[2, 2, 2, ..., 1, 2, 2],
[3, 2, 2, ..., 3, 2, 2],
[3, 2, 2, ..., 2, 2, 3]], dtype=uint16))
x1,y1,x2,y2 = 650, 300, 850, 500
vmin, vmax = 0, 6
show_zoomed(wide_green, green_darkfield, x1, y1, x2, y2, image_name='Wide Aperture Green Filter', cropped_image_name='Cropped Wide Aperture Green Filter', mm_per_pixel = (6.230210280811735e-07*1000), vmin=vmin, vmax=vmax)
(array([[2.11, 2.25, 2. , ..., 2.48, 2.42, 2.4 ],
[2.3 , 2.16, 2.02, ..., 2.51, 2.21, 2.39],
[1.93, 2.26, 1.84, ..., 2.63, 2.66, 2.45],
...,
[2.13, 2.59, 2.1 , ..., 2.36, 2.78, 2.56],
[2.3 , 2.34, 2.19, ..., 2.41, 2.5 , 2.53],
[2.32, 2.31, 2.48, ..., 2.54, 2.67, 2.49]]),
array([[ 0.15, 0.15, 0.02, ..., -0.04, 0. , 0.14],
[ 0.1 , 0.12, 0.12, ..., 0.07, 0.15, 0.19],
[-0.01, 0.04, -0.08, ..., 0.27, -0.02, -0.07],
...,
[ 0.17, 0.11, 0.16, ..., 0.02, -0.02, 0.2 ],
[ 0.08, 0. , 0.07, ..., -0.15, 0.06, 0.15],
[-0.04, 0.19, 0.22, ..., 0.22, 0.09, -0.01]]),
array([[2.42, 2.68, 2.5 , ..., 2.46, 2.67, 2.65],
[2.72, 2.69, 2.6 , ..., 2.29, 2.47, 2.71],
[4.48, 2.52, 2.51, ..., 2.39, 2.63, 2.54],
...,
[2.71, 2.65, 2.35, ..., 2.4 , 2.61, 2.7 ],
[2.45, 2.55, 2.56, ..., 2.59, 2.73, 2.53],
[2.79, 2.69, 2.4 , ..., 2.49, 2.55, 2.64]]),
array([[-0.04, 0. , -0.02, ..., 0.06, -0.05, -0.01],
[ 0.06, 0.01, 0.1 , ..., 0.15, 0.13, 0.27],
[-0.04, -0.02, -0.05, ..., -0.19, -0.11, -0.14],
...,
[ 0.11, 0.09, 0.01, ..., -0.12, -0.07, 0.04],
[ 0.09, -0.05, 0.08, ..., 0.09, 0.13, -0.01],
[ 0.15, -0.09, 0.02, ..., 0.05, 0.09, 0.04]]))
Example aperture position: wide open (100% open), medium aperture (60% open), small aperture (30% open)
medium_green = average_images(100)
np.save("medium_green", medium_green)
image number taken: 10 image number taken: 20 image number taken: 30 image number taken: 40 image number taken: 50 image number taken: 60 image number taken: 70 image number taken: 80 image number taken: 90 image number taken: 100
x1,y1,x2,y2 = 650, 300, 850, 500
vmin, vmax = 0, 6
show_zoomed(medium_green, green_darkfield, x1, y1, x2, y2, image_name='Medium Aperture Green Filter', cropped_image_name='Cropped Medium Aperture Green Filter', mm_per_pixel = (6.230210280811735e-07*1000), vmin=vmin, vmax=vmax)
(array([[2.08, 2.26, 1.91, ..., 2.44, 2.56, 2.52],
[2.42, 2.12, 2.18, ..., 2.45, 2.14, 2.32],
[2.12, 2.38, 2.02, ..., 2.5 , 2.65, 2.54],
...,
[2.2 , 2.49, 2.26, ..., 2.59, 2.93, 2.67],
[2.45, 2.46, 2.22, ..., 2.45, 2.7 , 2.55],
[2.27, 2.25, 2.58, ..., 2.65, 2.84, 2.7 ]]),
array([[ 0.12, 0.16, -0.07, ..., -0.08, 0.14, 0.26],
[ 0.22, 0.08, 0.28, ..., 0.01, 0.08, 0.12],
[ 0.18, 0.16, 0.1 , ..., 0.14, -0.03, 0.02],
...,
[ 0.24, 0.01, 0.32, ..., 0.25, 0.13, 0.31],
[ 0.23, 0.12, 0.1 , ..., -0.11, 0.26, 0.17],
[-0.09, 0.13, 0.32, ..., 0.33, 0.26, 0.2 ]]),
array([[2.55, 2.9 , 2.76, ..., 2.64, 2.78, 2.71],
[2.69, 2.75, 2.6 , ..., 2.44, 2.57, 2.7 ],
[4.53, 2.65, 2.65, ..., 2.59, 2.78, 2.68],
...,
[2.85, 2.8 , 2.56, ..., 2.42, 2.76, 2.77],
[2.45, 2.83, 2.57, ..., 2.53, 2.79, 2.75],
[2.81, 2.94, 2.48, ..., 2.58, 2.8 , 2.76]]),
array([[ 0.09, 0.22, 0.24, ..., 0.24, 0.06, 0.05],
[ 0.03, 0.07, 0.1 , ..., 0.3 , 0.23, 0.26],
[ 0.01, 0.11, 0.09, ..., 0.01, 0.04, 0. ],
...,
[ 0.25, 0.24, 0.22, ..., -0.1 , 0.08, 0.11],
[ 0.09, 0.23, 0.09, ..., 0.03, 0.19, 0.21],
[ 0.17, 0.16, 0.1 , ..., 0.14, 0.34, 0.16]]))
low_green = average_images(100)
np.save("low_green", low_green)
image number taken: 10 image number taken: 20 image number taken: 30 image number taken: 40 image number taken: 50 image number taken: 60 image number taken: 70 image number taken: 80 image number taken: 90 image number taken: 100
x1,y1,x2,y2 = 650, 300, 850, 500
vmin, vmax = 0, 0.4
show_zoomed(low_green, green_darkfield, x1, y1, x2, y2, image_name='Low Aperture Green Filter', cropped_image_name='Cropped Low Aperture Green Filter', mm_per_pixel = (6.230210280811735e-07*1000), vmin=vmin, vmax=vmax)
(array([[2.15, 2.2 , 2.09, ..., 2.55, 2.53, 2.46],
[2.39, 2.25, 2.16, ..., 2.53, 2.02, 2.19],
[2.02, 2.28, 2. , ..., 2.6 , 2.8 , 2.56],
...,
[2.05, 2.46, 2.14, ..., 2.55, 2.82, 2.48],
[2.36, 2.29, 2.11, ..., 2.39, 2.6 , 2.4 ],
[2.2 , 2.3 , 2.35, ..., 2.61, 2.71, 2.57]]),
array([[ 0.19, 0.1 , 0.11, ..., 0.03, 0.11, 0.2 ],
[ 0.19, 0.21, 0.26, ..., 0.09, -0.04, -0.01],
[ 0.08, 0.06, 0.08, ..., 0.24, 0.12, 0.04],
...,
[ 0.09, -0.02, 0.2 , ..., 0.21, 0.02, 0.12],
[ 0.14, -0.05, -0.01, ..., -0.17, 0.16, 0.02],
[-0.16, 0.18, 0.09, ..., 0.29, 0.13, 0.07]]),
array([[2.52, 2.9 , 2.59, ..., 2.69, 2.74, 2.8 ],
[2.69, 2.78, 2.45, ..., 2.19, 2.63, 2.6 ],
[4.47, 2.57, 2.45, ..., 2.6 , 2.71, 2.55],
...,
[2.69, 2.58, 2.37, ..., 2.48, 2.7 , 2.77],
[2.43, 2.67, 2.59, ..., 2.62, 2.72, 2.58],
[2.56, 2.79, 2.34, ..., 2.47, 2.61, 2.72]]),
array([[ 0.06, 0.22, 0.07, ..., 0.29, 0.02, 0.14],
[ 0.03, 0.1 , -0.05, ..., 0.05, 0.29, 0.16],
[-0.05, 0.03, -0.11, ..., 0.02, -0.03, -0.13],
...,
[ 0.09, 0.02, 0.03, ..., -0.04, 0.02, 0.11],
[ 0.07, 0.07, 0.11, ..., 0.12, 0.12, 0.04],
[-0.08, 0.01, -0.04, ..., 0.03, 0.15, 0.12]]))
vmin, vmax = 0, 200
show_zoomed(wide_green, green_darkfield, x1, y1, x2, y2, image_name='Wide Aperture Green Filter', cropped_image_name='Cropped Wide Aperture Green Filter', mm_per_pixel = (6.230210280811735e-07*1000), vmin=vmin, vmax=vmax)
vmin, vmax = 0, 6
show_zoomed(wide_green, green_darkfield, x1, y1, x2, y2, image_name='Wide Aperture Green Filter', cropped_image_name='Cropped Wide Aperture Green Filter', mm_per_pixel = (6.230210280811735e-07*1000), vmin=vmin, vmax=vmax)
vmin, vmax = 0, 200
show_zoomed(medium_green, green_darkfield, x1, y1, x2, y2, image_name='Medium Aperture Green Filter', cropped_image_name='Cropped Medium Aperture Green Filter', mm_per_pixel = (6.230210280811735e-07*1000), vmin=vmin, vmax=vmax)
vmin, vmax = 0, 6
show_zoomed(medium_green, green_darkfield, x1, y1, x2, y2, image_name='Medium Aperture Green Filter', cropped_image_name='Cropped Medium Aperture Green Filter', mm_per_pixel = (6.230210280811735e-07*1000), vmin=vmin, vmax=vmax)
vmin, vmax = 0, 200
show_zoomed(low_green, green_darkfield, x1, y1, x2, y2, image_name='Low Aperture Green Filter', cropped_image_name='Cropped Low Aperture Green Filter', mm_per_pixel = (6.230210280811735e-07*1000), vmin=vmin, vmax=vmax)
vmin, vmax = 0, 0.4
show_zoomed(low_green, green_darkfield, x1, y1, x2, y2, image_name='Low Aperture Green Filter', cropped_image_name='Cropped Low Aperture Green Filter', mm_per_pixel = (6.230210280811735e-07*1000), vmin=vmin, vmax=vmax)
(array([[2.15, 2.2 , 2.09, ..., 2.55, 2.53, 2.46],
[2.39, 2.25, 2.16, ..., 2.53, 2.02, 2.19],
[2.02, 2.28, 2. , ..., 2.6 , 2.8 , 2.56],
...,
[2.05, 2.46, 2.14, ..., 2.55, 2.82, 2.48],
[2.36, 2.29, 2.11, ..., 2.39, 2.6 , 2.4 ],
[2.2 , 2.3 , 2.35, ..., 2.61, 2.71, 2.57]]),
array([[ 0.19, 0.1 , 0.11, ..., 0.03, 0.11, 0.2 ],
[ 0.19, 0.21, 0.26, ..., 0.09, -0.04, -0.01],
[ 0.08, 0.06, 0.08, ..., 0.24, 0.12, 0.04],
...,
[ 0.09, -0.02, 0.2 , ..., 0.21, 0.02, 0.12],
[ 0.14, -0.05, -0.01, ..., -0.17, 0.16, 0.02],
[-0.16, 0.18, 0.09, ..., 0.29, 0.13, 0.07]]),
array([[2.52, 2.9 , 2.59, ..., 2.69, 2.74, 2.8 ],
[2.69, 2.78, 2.45, ..., 2.19, 2.63, 2.6 ],
[4.47, 2.57, 2.45, ..., 2.6 , 2.71, 2.55],
...,
[2.69, 2.58, 2.37, ..., 2.48, 2.7 , 2.77],
[2.43, 2.67, 2.59, ..., 2.62, 2.72, 2.58],
[2.56, 2.79, 2.34, ..., 2.47, 2.61, 2.72]]),
array([[ 0.06, 0.22, 0.07, ..., 0.29, 0.02, 0.14],
[ 0.03, 0.1 , -0.05, ..., 0.05, 0.29, 0.16],
[-0.05, 0.03, -0.11, ..., 0.02, -0.03, -0.13],
...,
[ 0.09, 0.02, 0.03, ..., -0.04, 0.02, 0.11],
[ 0.07, 0.07, 0.11, ..., 0.12, 0.12, 0.04],
[-0.08, 0.01, -0.04, ..., 0.03, 0.15, 0.12]]))
As the aperture increases, the intensity and size of the pinhole increases. More light is let into the camera through the aperture when it is at max opening, resulting in more sidelobes. At low aperture setting, less light is provided, thus the light is less intense; however, there are less side lobes, creating a sharper image. As a result, the best image is produced at a moderate aperture setting as it balances intensity and clarity.
Wide -> 0.006 mm in diameter
Medium -> 0.004 mm in diameter
Low -> practically nonexistent, approximately 0.001 mm
from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable
def plotsurface_mm(data, mm_per_pixel=(3.45e-6 * 1000), title="FFT Magnitude Surface", zlim=None, use_log=True):
# Remove DC spike
data_centered = data - np.mean(data)
fft_result = np.fft.fftshift(np.fft.fft2(data_centered))
# Magnitude (optionally log-scaled for dynamic range compression)
if use_log:
fft_magnitude = np.log1p(np.abs(fft_result))
else:
fft_magnitude = np.abs(fft_result)
# Frequency axes in cycles/mm
N, M = data.shape
fx = np.fft.fftshift(np.fft.fftfreq(M, d=mm_per_pixel)) # X in mm⁻¹
fy = np.fft.fftshift(np.fft.fftfreq(N, d=mm_per_pixel)) # Y in mm⁻¹
FX, FY = np.meshgrid(fx, fy)
# Plotting
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_surface(FX, FY, fft_magnitude, cmap='viridis', linewidth=0, antialiased=False)
ax.set_title(title, fontsize=14)
ax.set_xlabel("Frequency X (1/mm)", fontsize=12)
ax.set_ylabel("Frequency Y (1/mm)", fontsize=12)
ax.set_zlabel("Magnitude", fontsize=12)
if zlim:
ax.set_zlim(*zlim)
fig.colorbar(surf, shrink=0.5, aspect=10)
plt.tight_layout()
plt.show()
return fft_magnitude, fx, fy
def fft_diameter_widget(data, mm_per_pixel=(3.45e-6 * 1000), use_log=True):
# Preprocess: Remove DC spike
data_centered = data - np.mean(data)
fft_result = np.fft.fftshift(np.fft.fft2(data_centered))
fft_magnitude = np.log1p(np.abs(fft_result)) if use_log else np.abs(fft_result)
# Axes in mm⁻¹
N, M = data.shape
fx = np.fft.fftshift(np.fft.fftfreq(M, d=mm_per_pixel))
fy = np.fft.fftshift(np.fft.fftfreq(N, d=mm_per_pixel))
extent = [fx[0], fx[-1], fy[0], fy[-1]]
# Enable interactive mode
%matplotlib widget
fig = plt.figure(figsize=(10, 10))
ax = fig.add_axes([0.1, 0.3, 0.6, 0.6])
ax_divider = make_axes_locatable(ax)
cax = ax_divider.append_axes("right", size="7%", pad="2%")
im = ax.imshow(fft_magnitude, cmap='gray', origin='lower', extent=extent)
cb = fig.colorbar(im, cax=cax)
# Label axes with proper units
ax.set_xlabel("Frequency X (1/mm)")
ax.set_ylabel("Frequency Y (1/mm)")
# Set title
ax.set_title("FFT Magnitude (log scale)" if use_log else "FFT Magnitude")
hfig = display(fig, display_id=True)
plt.show()
plotsurface_mm(green_darkfield, mm_per_pixel=(6.230210280811735e-07*1000), title="Green Darkfield FFT Magnitude Surface", zlim=None, use_log=True)
plotsurface_mm(wide_green, mm_per_pixel=(6.230210280811735e-07*1000), title="Wide Green FFT Magnitude Surface", zlim=None, use_log=True)
plotsurface_mm(medium_green, mm_per_pixel=(6.230210280811735e-07*1000), title="Medium Green FFT Magnitude Surface", zlim=None, use_log=True)
plotsurface_mm(low_green, mm_per_pixel=(6.230210280811735e-07*1000), title="Low Green FFT Magnitude Surface", zlim=None, use_log=True)
(array([[7.70355832, 5.76228873, 5.39267725, ..., 7.04326664, 5.39267725,
5.76228873],
[7.1137036 , 7.51887916, 6.5861668 , ..., 7.65078791, 7.06136575,
6.71455728],
[7.5275313 , 7.23854157, 5.83198539, ..., 6.45295002, 6.891108 ,
5.5969573 ],
...,
[6.57858214, 4.40203326, 5.09294763, ..., 6.77092912, 6.78245264,
7.10624985],
[7.5275313 , 5.5969573 , 6.891108 , ..., 6.69803783, 5.83198539,
7.23854157],
[7.1137036 , 6.71455728, 7.06136575, ..., 6.89723192, 6.5861668 ,
7.51887916]]),
array([-802.54113018, -801.42648972, -800.31184926, ..., 799.1972088 ,
800.31184926, 801.42648972]),
array([-802.54113018, -801.0549429 , -799.56875562, ..., 798.08256834,
799.56875562, 801.0549429 ]))
!pip install ipympl
Collecting ipympl Obtaining dependency information for ipympl from https://files.pythonhosted.org/packages/86/6b/21c62f1b2b5e18c6553f7364487dfdf7eb7952b1000a1b7863c1f97819e9/ipympl-0.9.7-py3-none-any.whl.metadata Downloading ipympl-0.9.7-py3-none-any.whl.metadata (8.7 kB) Requirement already satisfied: ipython<10 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipympl) (8.12.2) Requirement already satisfied: ipywidgets<9,>=7.6.0 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipympl) (8.0.4) Requirement already satisfied: matplotlib<4,>=3.5.0 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipympl) (3.7.1) Requirement already satisfied: numpy in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipympl) (1.25.2) Requirement already satisfied: pillow in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipympl) (9.4.0) Requirement already satisfied: traitlets<6 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipympl) (5.7.1) Requirement already satisfied: backcall in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipython<10->ipympl) (0.2.0) Requirement already satisfied: decorator in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipython<10->ipympl) (5.1.1) Requirement already satisfied: jedi>=0.16 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipython<10->ipympl) (0.18.1) Requirement already satisfied: matplotlib-inline in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipython<10->ipympl) (0.1.6) Requirement already satisfied: pickleshare in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipython<10->ipympl) (0.7.5) Requirement already satisfied: prompt-toolkit!=3.0.37,<3.1.0,>=3.0.30 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipython<10->ipympl) (3.0.36) Requirement already satisfied: pygments>=2.4.0 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipython<10->ipympl) (2.15.1) Requirement already satisfied: stack-data in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipython<10->ipympl) (0.2.0) Requirement already satisfied: pexpect>4.3 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipython<10->ipympl) (4.8.0) Requirement already satisfied: appnope in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipython<10->ipympl) (0.1.2) Requirement already satisfied: ipykernel>=4.5.1 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipywidgets<9,>=7.6.0->ipympl) (6.25.0) Requirement already satisfied: widgetsnbextension~=4.0 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipywidgets<9,>=7.6.0->ipympl) (4.0.5) Requirement already satisfied: jupyterlab-widgets~=3.0 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipywidgets<9,>=7.6.0->ipympl) (3.0.5) Requirement already satisfied: contourpy>=1.0.1 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from matplotlib<4,>=3.5.0->ipympl) (1.0.5) Requirement already satisfied: cycler>=0.10 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from matplotlib<4,>=3.5.0->ipympl) (0.11.0) Requirement already satisfied: fonttools>=4.22.0 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from matplotlib<4,>=3.5.0->ipympl) (4.25.0) Requirement already satisfied: kiwisolver>=1.0.1 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from matplotlib<4,>=3.5.0->ipympl) (1.4.4) Requirement already satisfied: packaging>=20.0 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from matplotlib<4,>=3.5.0->ipympl) (23.0) Requirement already satisfied: pyparsing>=2.3.1 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from matplotlib<4,>=3.5.0->ipympl) (3.0.9) Requirement already satisfied: python-dateutil>=2.7 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from matplotlib<4,>=3.5.0->ipympl) (2.8.2) Requirement already satisfied: comm>=0.1.1 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipykernel>=4.5.1->ipywidgets<9,>=7.6.0->ipympl) (0.1.2) Requirement already satisfied: debugpy>=1.6.5 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipykernel>=4.5.1->ipywidgets<9,>=7.6.0->ipympl) (1.6.7) Requirement already satisfied: jupyter-client>=6.1.12 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipykernel>=4.5.1->ipywidgets<9,>=7.6.0->ipympl) (7.4.9) Requirement already satisfied: jupyter-core!=5.0.*,>=4.12 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipykernel>=4.5.1->ipywidgets<9,>=7.6.0->ipympl) (5.3.0) Requirement already satisfied: nest-asyncio in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipykernel>=4.5.1->ipywidgets<9,>=7.6.0->ipympl) (1.5.6) Requirement already satisfied: psutil in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipykernel>=4.5.1->ipywidgets<9,>=7.6.0->ipympl) (5.9.0) Requirement already satisfied: pyzmq>=20 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipykernel>=4.5.1->ipywidgets<9,>=7.6.0->ipympl) (23.2.0) Requirement already satisfied: tornado>=6.1 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from ipykernel>=4.5.1->ipywidgets<9,>=7.6.0->ipympl) (6.3.2) Requirement already satisfied: parso<0.9.0,>=0.8.0 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from jedi>=0.16->ipython<10->ipympl) (0.8.3) Requirement already satisfied: ptyprocess>=0.5 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from pexpect>4.3->ipython<10->ipympl) (0.7.0) Requirement already satisfied: wcwidth in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from prompt-toolkit!=3.0.37,<3.1.0,>=3.0.30->ipython<10->ipympl) (0.2.5) Requirement already satisfied: six>=1.5 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from python-dateutil>=2.7->matplotlib<4,>=3.5.0->ipympl) (1.16.0) Requirement already satisfied: executing in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from stack-data->ipython<10->ipympl) (0.8.3) Requirement already satisfied: asttokens in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from stack-data->ipython<10->ipympl) (2.0.5) Requirement already satisfied: pure-eval in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from stack-data->ipython<10->ipympl) (0.2.2) Requirement already satisfied: entrypoints in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from jupyter-client>=6.1.12->ipykernel>=4.5.1->ipywidgets<9,>=7.6.0->ipympl) (0.4) Requirement already satisfied: platformdirs>=2.5 in /Users/jayvik/anaconda3/lib/python3.11/site-packages (from jupyter-core!=5.0.*,>=4.12->ipykernel>=4.5.1->ipywidgets<9,>=7.6.0->ipympl) (3.10.0) Downloading ipympl-0.9.7-py3-none-any.whl (515 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 515.7/515.7 kB 14.6 MB/s eta 0:00:00 Installing collected packages: ipympl Successfully installed ipympl-0.9.7
fft_diameter_widget(wide_green, mm_per_pixel=(6.230210280811735e-07*1000), use_log=True)
fft_diameter_widget(medium_green, mm_per_pixel=(6.230210280811735e-07*1000), use_log=True)
fft_diameter_widget(low_green, mm_per_pixel=(6.230210280811735e-07*1000), use_log=True)
Estimated diameter in fourier domain:
Wide -> -0.62 to 0.46 => 1.08 (1/mm units)
Medium -> -0.6 to 0.33 => 0.93 (1/mm units)
Low -> -0.55 to 0.36 => 0.91 (1/mm units)
We are being asked to estimate how far from the center (DC) the frequency content extends from the center.
For each dataset, we plotted the absolute value of the 2D Fourier transform using ax.plot_surface(). The resulting plots show a narrow, centered spike, which corresponds to the DC component and reflects the mean intensity of the original image. This spike is very sharp and dominates the center of the plot.
Beyond the spike, we observed significant background noise spread throughout the frequency domain, suggesting a lack of strong periodic structure in the data. The noise appears relatively uniform, with no clear secondary peaks or dominant frequency components. This indicates that most of the energy is concentrated at low frequencies, with no highly structured repeating patterns.
blue_darkfield = average_images(50)
np.save("blue_darkfield", blue_darkfield)
image number taken: 10 image number taken: 20 image number taken: 30 image number taken: 40 image number taken: 50
x1,y1,x2,y2 = 650, 300, 850, 500
vmin, vmax = 0, 10
show_zoomed(blue_darkfield, blue_darkfield, x1, y1, x2, y2, image_name='Blue Darkfield', cropped_image_name='Cropped Blue Darkfield', mm_per_pixel = (6.230210280811735e-07*1000), vmin=vmin, vmax=vmax)
(array([[1.06, 1.04, 1.12, ..., 1.08, 1.1 , 1.02],
[1.04, 1. , 1.04, ..., 1. , 1.02, 1.06],
[1.02, 1.04, 1. , ..., 1.06, 1.06, 1.02],
...,
[1. , 1.06, 1.02, ..., 1.06, 1. , 1.08],
[1.02, 1.08, 1.08, ..., 1.02, 1.02, 1.06],
[1. , 1. , 1.12, ..., 1.06, 1.04, 1.14]]),
array([[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.]]),
array([[1. , 1.04, 1.1 , ..., 1.08, 1.04, 1.02],
[1. , 1.02, 1.08, ..., 1.02, 1.04, 1. ],
[1. , 1. , 1.16, ..., 1.06, 1. , 1. ],
...,
[1.08, 1.12, 1.04, ..., 1. , 1.02, 1.02],
[1.02, 1.04, 1.04, ..., 1.08, 1. , 1. ],
[1.06, 1.06, 1.04, ..., 1.08, 1.02, 1. ]]),
array([[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.]]))
wide_blue = average_images(100)
np.save("wide_blue", wide_blue)
image number taken: 10 image number taken: 20 image number taken: 30 image number taken: 40 image number taken: 50 image number taken: 60 image number taken: 70 image number taken: 80 image number taken: 90 image number taken: 100
x1,y1,x2,y2 = 650, 300, 850, 500
vmin, vmax = 0, 50
show_zoomed(wide_blue_defocus1, blue_darkfield, x1, y1, x2, y2, image_name='Wide Aperture Blue Filter', cropped_image_name='Wide Aperture Blue Filter', mm_per_pixel = (6.230210280811735e-07*1000), vmin=vmin, vmax=vmax)
plotsurface_mm(wide_blue_defocus1, mm_per_pixel=(6.230210280811735e-07*1000), title="Wide Blue FFT Magnitude Surface", zlim=None, use_log=True)
(array([[7.06510939, 5.60920067, 5.58068035, ..., 7.06085835, 5.58068035,
5.60920067],
[7.29679224, 7.48426277, 5.67334227, ..., 7.2900458 , 7.15603886,
7.31066241],
[7.17482634, 6.84272762, 5.60811161, ..., 6.0320111 , 6.29947346,
5.4295849 ],
...,
[6.69317839, 5.93184359, 5.75635544, ..., 6.76669259, 7.04837687,
7.25640549],
[7.17482634, 5.4295849 , 6.29947346, ..., 6.2652934 , 5.60811161,
6.84272762],
[7.29679224, 7.31066241, 7.15603886, ..., 6.9660175 , 5.67334227,
7.48426277]]),
array([-802.54113018, -801.42648972, -800.31184926, ..., 799.1972088 ,
800.31184926, 801.42648972]),
array([-802.54113018, -801.0549429 , -799.56875562, ..., 798.08256834,
799.56875562, 801.0549429 ]))
fft_diameter_widget(wide_blue_defocus1, mm_per_pixel=(6.230210280811735e-07*1000), use_log=True)
Estimated diameter in image -> 0.02 mm in diameter
Estimated diameter in fourier domain -> -183 to 187 => 370 (1/mm)
In comparison to the filter 2 case, filter 1 has a lower wavelength. As a result, the image must be less bright/intense but provide greater resolution. Unfortunately, our data using the filter 1 was too low brightness and we were limited by our setup. Thus the images provided are from a no filter setup. In theory, we would expect a lower brightness but greater clarity / less blur in comparison to filter 2
defocus_level1_nofilter = continuous_capture_jet(save_name="defocus_level1_nofilter")
--------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) C:\Users\BUILD_~1\AppData\Local\Temp/ipykernel_37740/2720362854.py in <module> ----> 1 defocus_level1_nofilter = continuous_capture_jet(save_name="defocus_level1_nofilter") C:\Users\BUILD_~1\AppData\Local\Temp/ipykernel_37740/1197984356.py in continuous_capture_jet(save_name) 47 break 48 cv2.destroyAllWindows() ---> 49 return image 50 51 def continuous_capture(save_name="saved_image.png"): UnboundLocalError: local variable 'image' referenced before assignment
wide_blue_defocus1 = average_images(100)
np.save("wide_blue_defocus1", wide_blue_defocus1)
image number taken: 10 image number taken: 20 image number taken: 30 image number taken: 40 image number taken: 50 image number taken: 60 image number taken: 70 image number taken: 80 image number taken: 90 image number taken: 100
x1,y1,x2,y2 = 650, 300, 850, 500
vmin, vmax = 0, 50
show_zoomed(wide_blue_defocus1, blue_darkfield, x1, y1, x2, y2, image_name='Wide Aperture Defocus 1 Blue Filter', cropped_image_name='Cropped Wide Aperture Defocus 1 Blue Filter', mm_per_pixel = (6.230210280811735e-07*1000), vmin=vmin, vmax=vmax)
(array([[1.88, 1.97, 1.78, ..., 2.17, 2.13, 2.06],
[2.12, 1.82, 1.92, ..., 2.11, 1.9 , 2.05],
[1.8 , 1.96, 1.74, ..., 2.03, 2.31, 2.25],
...,
[1.9 , 2.32, 2.06, ..., 2.25, 2.59, 2.26],
[2.2 , 2.17, 2.1 , ..., 2.19, 2.46, 2.33],
[2.12, 2.02, 2.31, ..., 2.47, 2.57, 2.47]]),
array([[0.82, 0.93, 0.66, ..., 1.09, 1.03, 1.04],
[1.08, 0.82, 0.88, ..., 1.11, 0.88, 0.99],
[0.78, 0.92, 0.74, ..., 0.97, 1.25, 1.23],
...,
[0.9 , 1.26, 1.04, ..., 1.19, 1.59, 1.18],
[1.18, 1.09, 1.02, ..., 1.17, 1.44, 1.27],
[1.12, 1.02, 1.19, ..., 1.41, 1.53, 1.33]]),
array([[2.08, 2.58, 2.36, ..., 2.73, 2.7 , 2.65],
[2.35, 2.36, 2.25, ..., 2.31, 2.58, 2.62],
[3.98, 2.25, 2.34, ..., 2.91, 3.03, 2.74],
...,
[2.66, 2.58, 2.3 , ..., 2.63, 2.93, 2.9 ],
[2.28, 2.55, 2.5 , ..., 2.87, 2.89, 2.66],
[2.6 , 2.69, 2.24, ..., 2.85, 2.72, 2.84]]),
array([[1.08, 1.54, 1.26, ..., 1.65, 1.66, 1.63],
[1.35, 1.34, 1.17, ..., 1.29, 1.54, 1.62],
[2.98, 1.25, 1.18, ..., 1.85, 2.03, 1.74],
...,
[1.58, 1.46, 1.26, ..., 1.63, 1.91, 1.88],
[1.26, 1.51, 1.46, ..., 1.79, 1.89, 1.66],
[1.54, 1.63, 1.2 , ..., 1.77, 1.7 , 1.84]]))
fft_diameter_widget(wide_blue_defocus1, mm_per_pixel=(6.230210280811735e-07*1000), use_log=True)
Estimated diameter in image -> 0.02 mm in diameter
Estimated diameter in fourier domain -> 370 (1/mm)
defocus_level2_nofilter = continuous_capture_jet(save_name="defocus_level2_nofilter")
wide_blue_defocus2 = average_images(100)
np.save("wide_blue_defocus2", wide_blue_defocus2)
image number taken: 10 image number taken: 20 image number taken: 30 image number taken: 40 image number taken: 50 image number taken: 60 image number taken: 70 image number taken: 80 image number taken: 90 image number taken: 100
x1,y1,x2,y2 = 650, 300, 850, 500
vmin, vmax = 0, 50
show_zoomed(wide_blue_defocus2, blue_darkfield, x1, y1, x2, y2, image_name='Wide Aperture Defocus 2 Blue Filter', cropped_image_name='Cropped Wide Aperture Defocus 2 Blue Filter', mm_per_pixel = (6.230210280811735e-07*1000), vmin=vmin, vmax=vmax)
plotsurface_mm(wide_blue_defocus2, mm_per_pixel=(6.230210280811735e-07*1000), title="Wide Blue FFT Magnitude Surface", zlim=None, use_log=True)
(array([[7.35931486, 5.03039379, 4.7957104 , ..., 7.24338464, 4.7957104 ,
5.03039379],
[7.2479092 , 7.30555642, 6.26592198, ..., 7.3979492 , 6.93285622,
7.21588352],
[7.37849541, 7.04258892, 3.53522466, ..., 6.11122545, 6.31725558,
5.80254457],
...,
[6.52467716, 5.37525034, 5.19456825, ..., 6.69186334, 6.89109544,
6.95102863],
[7.37849541, 5.80254457, 6.31725558, ..., 6.59528099, 3.53522466,
7.04258892],
[7.2479092 , 7.21588352, 6.93285622, ..., 6.73403838, 6.26592198,
7.30555642]]),
array([-802.54113018, -801.42648972, -800.31184926, ..., 799.1972088 ,
800.31184926, 801.42648972]),
array([-802.54113018, -801.0549429 , -799.56875562, ..., 798.08256834,
799.56875562, 801.0549429 ]))
fft_diameter_widget(wide_blue_defocus2, mm_per_pixel=(6.230210280811735e-07*1000), use_log=True)
Estimated diameter in image -> 0.03 mm in diameter
Estimated diameter in fourier domain -> -104 to 94 => 198 (1/mm)
defocus_level3_nofilter = continuous_capture_jet(save_name="defocus_level3_nofilter")
wide_blue_defocus3 = average_images(100)
np.save("wide_blue_defocus3", wide_blue_defocus3)
image number taken: 10 image number taken: 20 image number taken: 30 image number taken: 40 image number taken: 50 image number taken: 60 image number taken: 70 image number taken: 80 image number taken: 90 image number taken: 100
x1,y1,x2,y2 = 650, 300, 850, 500
vmin, vmax = 0, 50
show_zoomed(wide_blue_defocus3, blue_darkfield, x1, y1, x2, y2, image_name='Wide Aperture Defocus 3 Blue Filter', cropped_image_name='Cropped Wide Aperture Defocus 3 Blue Filter', mm_per_pixel = (6.230210280811735e-07*1000), vmin=vmin, vmax=vmax)
plotsurface_mm(wide_blue_defocus3, mm_per_pixel=(6.230210280811735e-07*1000), title="Wide Blue FFT Magnitude Surface", zlim=None, use_log=True)
(array([[7.3746729 , 5.07281999, 4.61063551, ..., 7.24505956, 4.61063551,
5.07281999],
[7.23783559, 7.3245577 , 6.18924497, ..., 7.38554641, 6.89698156,
7.12650132],
[7.38815702, 7.12694897, 3.50054139, ..., 6.24748086, 6.17120417,
5.84290459],
...,
[6.42817087, 4.380206 , 5.39156898, ..., 6.53053106, 6.72804947,
7.00524287],
[7.38815702, 5.84290459, 6.17120417, ..., 6.49996875, 3.50054139,
7.12694897],
[7.23783559, 7.12650132, 6.89698156, ..., 6.7017887 , 6.18924497,
7.3245577 ]]),
array([-802.54113018, -801.42648972, -800.31184926, ..., 799.1972088 ,
800.31184926, 801.42648972]),
array([-802.54113018, -801.0549429 , -799.56875562, ..., 798.08256834,
799.56875562, 801.0549429 ]))
fft_diameter_widget(wide_blue_defocus3, mm_per_pixel=(6.230210280811735e-07*1000), use_log=True)
Estimated diameter in image -> 0.035 mm in diameter
Estimated diameter in fourier domain -> -58 to 82 => 140 (1/mm)
As the data becomes more and more defocused, the pinhole image shown from the camera becomes larger but gets less intense and more blurry. This is seen in the diameters of the spatial domain graphs as they increase from 0.02 to 0.03 and 0.035 mm. In contrast, the fourier domain rings get smaller and more narrow. Since there is less detail and the images are more blurry, there is less higher frequency in the fourier domain, leaving a narrow spike at the lower frequencies. This is seen as the diameters from 370, 198, to 140 (1/mm).
In comparison to the filter 2 case, filter 1 has a lower wavelength. As a result, the image must be less bright/intense but provide greater resolution. Unfortunately, our data using the filter 1 was too low brightness and we were limited by our setup. Thus the images provided are from a no filter setup. In theory, we would expect a lower brightness but greater clarity / less blur in comparison to filter 2
c.set_exposure(1000)
filter1_star = continuous_capture("filter1_star")
np.save("filter1_star", filter1_star)
darkfield = np.zeros((1080, 1440))
filter1_star_zoomed = show_zoomed(filter1_star, darkfield, 700, 300, 900, 500)
filter3_star = continuous_capture("filter3_star")
np.save("filter3_star", filter3_star)
darkfield = np.zeros((1080, 1440))
filter3_star_zoomed = show_zoomed(filter3_star, darkfield, 700, 300, 900, 500)
green_darkfield = np.load("Lab3_IMAGES/green_darkfield.npy")
wide_green = np.load("Lab3_IMAGES/wide_green.npy")
medium_green = np.load("Lab3_IMAGES/medium_green.npy")
low_green = np.load("Lab3_IMAGES/low_green.npy")
blue_darkfield = np.load("Lab3_IMAGES/blue_darkfield.npy")
wide_blue = np.load("Lab3_IMAGES/wide_blue.npy")
wide_blue_defocus1 = np.load("Lab3_IMAGES/wide_blue_defocus1.npy")
wide_blue_defocus2 = np.load("Lab3_IMAGES/wide_blue_defocus2.npy")
wide_blue_defocus3 = np.load("Lab3_IMAGES/wide_blue_defocus3.npy")
filter1_star = np.load("Lab3_IMAGES/filter1_star.npy")
filter3_star = np.load("Lab3_IMAGES/filter3_star.npy")
The orange filter image has a greater intensity because the filter lets more light through. This results in a brighter overall image. Longer wavelengths (600 nm for orange compared to 460 nm for filter 1) tend to penetrate better and scatter less, resulting in brighter images with more uniform illumination. This might make the orange image appear more intense, even if it has worse resolution.
However, the orange (600 nm) filter has better resolution as well. This is unexpected as shorter wavelengths tend to have reduced diffraction and the image should be clearer for the filter 1. The filter 3 lens should be must more blurry because the orange filter has a wavelength of 600 nm which is greater than 460 nm of filter 1 and thus would have greater diffraction. The reason our images look like this is likely due to the cleanliness of the filters themselves. Repeating this test after cleaning the filter may produce results more consistent with current understanding and our hypothesis.